﻿using ImageK.Gui;
using ImageK.IO;
using ImageK.Java;
using ImageK.Measure;
using ImageK.Plugin.Frame;
using ImageK.Plugin;
using ImageK.Process;
using ImageK.Util;
using ImageK;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO.Packaging;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ImageK.Java.Extensions;
using FileInfo = ImageK.IO.FileInfo;

namespace ImageK.Plugin
{
    /** Implements the File/Import/Image Sequence command, which
        opens a folder of images as a stack. */
    public class FolderOpener: PlugIn {
	    private static int MAX_SEPARATE = 100;
        private static string DIR_KEY = "import.sequence.dir";
	    private static string[] types = {"default", "16-bit", "32-bit", "RGB"};
        private static string[] excludedTypes = { ".txt", ".lut", ".roi", ".pty", ".hdr", ".java", ".ijm", ".py", ".js", ".bsh", ".xml", ".rar", ".h5", ".doc", ".xls" };
        private static bool staticSortFileNames = true;
        private static bool staticOpenAsVirtualStack;
        private bool convertToGrayscale;  //unused
        private bool _sortFileNames = true;
        private bool _sortByMetaData = true;
        private bool _openAsVirtualStack;
        private String directory;
        private bool directorySet;
        private String filter;
        private String legacyRegex;
        private FileInfo fi;
        private String info1;
        private ImagePlus image;
        private bool saveImage;
        private long t0;
        private int stackWidth, stackHeight;
        private int bitDepth;
        private int defaultBitDepth;
        private int nFiles = 0;
        private int start = 1;
        private int step = 1;
        private double scale = 100.0;
        private bool _openAsSeparateImages;
        private bool runningOpen;
        private TextBox dirField, filterField, startField, countField, stepField;


        /** Opens the images in the specified directory as a stack. Displays
            directory chooser and options dialogs if the argument is null. */
        public static ImagePlus open(String path)
        {
            return open(path, null);
        }

        /** Opens the images in the specified directory as a stack. Opens
            the images as a virtual stack if the 'options' string contains
            'virtual' or 'use'. Add ' filter=abc' to the options string to only open
            images with, for example, 'abc' in their name. The image type, start,
            step, count and scale can also be set, for example
            "bitdepth=32 start=10 step=2 count=10 scale=50".
            Add ' noMetaSort' to disable sorting of DICOM stacks by series
            number (0020,0011). Displays the Import/Sequence dialog if the
            the 'path' argument is null.
        */
        public static ImagePlus open(string path, string options)
        {
            FolderOpener fo = new FolderOpener();
            fo.saveImage = true;
            fo.setOptions(options);
            fo.run(path);
            return fo.image;
        }

        /** Opens the images in the specified directory as a widthxheight stack.
            Opens the images as a virtual stack if the 'options' string contains
            'virtual'. Add ' filter=abc' to the options string to only open
            images with, for example, 'abc' in their name. The image type, start,
            step, count and scale can also be set, for example
            "bitdepth=32 start=10 step=2 count=10 scale=50".
        */
        public static ImagePlus open(String path, int width, int height, String options)
        {
            FolderOpener fo = new FolderOpener();
            fo.saveImage = true;
            fo.stackWidth = width;
            fo.stackHeight = height;
            fo.setOptions(options);
            fo.run(path);
            return fo.image;
        }

        private void setOptions(String options)
        {
            if (options==null)
                return;
            _openAsVirtualStack = options.Contains("virtual") || options.Contains("use");
            if (options.Contains("noMetaSort"))
                _sortByMetaData = false;
            bitDepth = (int)Tools.getNumberFromList(options, "bitdepth=", 0);
            filter = Macro.getValue(options, "filter", "");
            this.start = (int)Tools.getNumberFromList(options, "start=", 1);
            this.step = (int)Tools.getNumberFromList(options, "step=", 1);
            this.scale = Tools.getNumberFromList(options, "scale=", 100);
            this.nFiles = (int)Tools.getNumberFromList(options, "count=", 0);
            if (options.Contains(" open"))
            {
                this._openAsSeparateImages = true;
                this._openAsVirtualStack = true;
                this.saveImage = false;
                this.runningOpen = true;
            }
        }

        /** Opens the images in the specified directory as a stack. Displays
            directory chooser and options dialogs if the argument is null. */
        public ImagePlus openFolder(String path)
        {
            saveImage = true;
            run(path);
            return image;
        }

        public void run(string arg)
        {
            bool isMacro = Macro.GetOptions()!=null;
            if (!directorySet)
                directory = null;
            if (arg!=null && !arg.Equals(""))
                directory = arg;
            else
            {
                if (!isMacro)
                {
                    _sortFileNames = staticSortFileNames;
                    _openAsVirtualStack = staticOpenAsVirtualStack;
                }
                arg = null;
                string macroOptions = Macro.GetOptions();
                if (macroOptions!=null)
                {
                    legacyRegex = Macro.getValue(macroOptions, "or", "");
                    if (legacyRegex.Equals(""))
                        legacyRegex = null;
                }
            }
            if (arg==null)
            {
                if (!showDialog()) return;
            }
            if (string.IsNullOrEmpty(directory))
            {
                error("No directory specified.     ");
                return;
            }
            DirectoryInfo file = new DirectoryInfo(directory);
            String[] list = file.GetDirectories().Select(d=>d.FullName).ToArray();
            if (list.Length == 0)
            {
                var parent = file.Parent;
                if (parent!=null)
                {
                    file = new DirectoryInfo(parent.FullName);
                    list = file.GetDirectories().Select(d => d.FullName).ToArray();
                }
                if (list.Length > 0)
                    directory = parent.FullName;
                else
                {
                    error("Directory not found: "+directory);
                    return;
                }
            }
            if (!(directory.EndsWith("/")||directory.EndsWith("\\")))
                directory += "/";
            if (arg==null && !isMacro)
                Prefs.set(DIR_KEY, directory);
            //remove subdirectories from list
            ArrayList fileList = new ArrayList();
            for (int i = 0; i<list.Length; i++)
            {
                if (File.Exists(directory + list[i]))
                {
                    fileList.Add(list[i]);
                }
            }
            if (fileList.Count<list.Length)
                list = (String[])fileList.ToArray();

            String title = directory;
            if (title.EndsWith(Path.DirectorySeparatorChar) || title.EndsWith("/"))
                title = title.Substring(0, title.Length-1);
            int index = title.LastIndexOf(Path.DirectorySeparatorChar);
            if (index!=-1)
                title = title.Substring(index + 1);
            else
            {
                index = title.LastIndexOf("/");
                if (index!=-1)
                    title = title.Substring(index + 1);
            }
            if (title.EndsWith(":"))
                title = title.Substring(0, title.Length-1);

            list = trimFileList(list);
            if (list==null)
                return;
            string pluginName = "Sequence Reader";
            if (legacyRegex!=null)
                pluginName += "(legacy)";
            list = getFilteredList(list, filter, pluginName);
            if (list==null)
                return;
            if (_sortFileNames || IJ.isMacOSX())
                list = StringSorter.sortNumerically(list);
            if (IJ.debugMode) IJ.log("FolderOpener: "+directory+" ("+list.Length+" files)");
            int width = 0, height = 0, stackSize = 1;
            ImageStack stack = null;
            double min = double.MaxValue;
            double max = -double.MaxValue;
            Calibration cal = null;
            bool allSameCalibration = true;
            IJ.resetEscape();
            Overlay overlay = null;
            if (this.nFiles==0)
                this.nFiles = list.Length;
            bool dicomImages = false;
            try
            {
                for (int i = 0; i<list.Length; i++)
                {
                    Opener opener = new Opener();
                    opener.setSilentMode(true);
                    IJ.redirectErrorMessages(true);
                    ImagePlus imp1 = opener.openTempImage(directory, list[i]);
                    IJ.redirectErrorMessages(false);
                    if (imp1!=null)
                    {
                        width = imp1.getWidth();
                        height = imp1.getHeight();
                        if (this.bitDepth==0)
                        {
                            this.bitDepth = imp1.getBitDepth();
                            this.defaultBitDepth = bitDepth;
                        }
                        String info = (String)imp1.getProperty("Info");
                        if (info!=null && info.Contains("7FE0,0010"))
                            dicomImages = true;
                        break;
                    }
                }
                if (width==0)
                {
                    error("This folder does not appear to contain\n"
                    + "any TIFF, JPEG, BMP, DICOM, GIF, FITS or PGM files.\n \n"
                    + "   \""+directory+"\"");
                    return;
                }
                IJ.ShowStatus("");
                t0 = DateTime.UtcNow.currentTimeMillis();
                if (dicomImages && !IJ.isMacOSX() && !_sortFileNames)
                    list = StringSorter.sortNumerically(list);

                if (this.nFiles<1)
                    this.nFiles = list.Length;
                if (this.start<1 || this.start>list.Length)
                    this.start = 1;
                if (this.start+this.nFiles-1>list.Length)
                    this.nFiles = list.Length-this.start+1;
                int count = 0;
                int counter = 0;
                ImagePlus imp = null;
                bool firstMessage = true;
                bool fileInfoStack = false;

                // open images as stack
                for (int i = this.start-1; i<list.Length; i++)
                {
                    if ((counter++%this.step)!=0)
                        continue;
                    Opener opener = new Opener();
                    opener.setSilentMode(true);
                    IJ.redirectErrorMessages(true);
                    if ("RoiSet.zip".Equals(list[i]))
                    {
                        IJ.open(directory+list[i]);
                        imp = null;
                    }
                    else if (!_openAsVirtualStack||stack==null)
                    {
                        imp = opener.openTempImage(directory, list[i]);
                        stackSize = imp!=null ? imp.getStackSize() : 1;
                    }
                    IJ.redirectErrorMessages(false);
                    if (imp!=null && stack==null)
                    {
                        width = imp.getWidth();
                        height = imp.getHeight();
                        if (stackWidth>0 && stackHeight>0)
                        {
                            width = stackWidth;
                            height = stackHeight;
                        }
                        if (bitDepth==0)
                            bitDepth = imp.getBitDepth();
                        fi = imp.getOriginalFileInfo();
                        ImageProcessor ip = imp.getProcessor();
                        min = ip.getMin();
                        max = ip.getMax();
                        cal = imp.getCalibration();
                        ColorModel cm = imp.getProcessor().getColorModel();
                        if (_openAsVirtualStack)
                        {
                            if (stackSize>1)
                            {
                                stack = new FileInfoVirtualStack();
                                fileInfoStack = true;
                            }
                            else
                            {
                                if (stackWidth>0 && stackHeight>0)
                                    stack = new VirtualStack(stackWidth, stackHeight, cm, directory);
                                else
                                    stack = new VirtualStack(width, height, cm, directory);
                            }
                        }
                        else if (this.scale<100.0)
                            stack = new ImageStack((int)(width*this.scale/100.0), (int)(height*this.scale/100.0), cm);
                        else
                            stack = new ImageStack(width, height, cm);
                        if (bitDepth!=0)
                            stack.setBitDepth(bitDepth);
                        info1 = (String)imp.getProperty("Info");
                    }
                    if (imp==null)
                        continue;
                    if (imp.getWidth()!=width || imp.getHeight()!=height)
                    {
                        if (stackWidth>0 && stackHeight>0)
                        {
                            ImagePlus imp2 = imp.createImagePlus();
                            ImageProcessor ip = imp.getProcessor();
                            ImageProcessor ip2 = ip.createProcessor(width, height);
                            ip2.insert(ip, 0, 0);
                            imp2.setProcessor(ip2);
                            imp = imp2;
                        }
                        else
                        {
                            IJ.log(list[i] + ": wrong size; "+width+"x"+height+" expected, "+imp.getWidth()+"x"+imp.getHeight()+" found");
                            continue;
                        }
                    }
                    String label = imp.getTitle();
                    if (stackSize==1)
                    {
                        String info = (String)imp.getProperty("Info");
                        if (info!=null)
                        {
                            if (useInfo(info))
                                label += "\n" + info;
                        }
                        else if (imp.getStackSize()>0)
                        {
                            String sliceLabel = imp.getStack().getSliceLabel(1);
                            if (useInfo(sliceLabel))
                                label =  sliceLabel;
                        }
                    }
                    if (Math.Abs(imp.getCalibration().pixelWidth-cal.pixelWidth)>0.0000000001)
                        allSameCalibration = false;
                    ImageStack inputStack = imp.getStack();
                    Overlay overlay2 = imp.getOverlay();
                    if (overlay2!=null && !_openAsVirtualStack)
                    {
                        if (overlay==null)
                            overlay = new Overlay();
                        for (int j = 0; j<overlay2.size(); j++)
                        {
                            Roi roi = overlay2.get(j);
                            int position = roi.getPosition();
                            if (position==0)
                                roi.setPosition(count+1);
                            overlay.add(roi);
                        }
                    }
                    if (_openAsVirtualStack)
                    {
                        if (fileInfoStack)
                            openAsFileInfoStack((FileInfoVirtualStack)stack, directory+list[i]);
                        else
                            ((VirtualStack)stack).addSlice(list[i]);
                    }
                    else
                    {
                        for (int slice = 1; slice<=stackSize; slice++)
                        {
                            int bitDepth2 = imp.getBitDepth();
                            String label2 = label;
                            ImageProcessor ip = null;
                            if (stackSize>1)
                            {
                                String sliceLabel = inputStack.getSliceLabel(slice);
                                if (sliceLabel!=null && sliceLabel.Length<=15)
                                    label2 += ":"+sliceLabel;
                                else if (label2!=null && !label2.Equals(""))
                                    label2 += ":"+slice;
                            }
                            ip = inputStack.getProcessor(slice);
                            if (bitDepth2!=bitDepth)
                            {
                                if (dicomImages && bitDepth==16 && bitDepth2==32 && this.scale==100)
                                {
                                    ip = ip.convertToFloat();
                                    bitDepth = 32;
                                    ImageStack stack2 = new ImageStack(width, height, stack.getColorModel());
                                    for (int n = 1; n<=stack.size(); n++)
                                    {
                                        ImageProcessor ip2 = stack.getProcessor(n);
                                        ip2 = ip2.convertToFloat();
                                        ip2.subtract(32768);
                                        String sliceLabel = stack.getSliceLabel(n);
                                        stack2.addSlice(sliceLabel, ip2.convertToFloat());
                                    }
                                    stack = stack2;
                                }
                            }
                            if (this.scale<100.0)
                                ip = ip.resize((int)(width*this.scale/100.0), (int)(height*this.scale/100.0));
                            if (ip.getMin()<min) min = ip.getMin();
                            if (ip.getMax()>max) max = ip.getMax();
                            stack.addSlice(label2, ip);
                        }
                    }
                    count++;
                    IJ.ShowStatus("!"+count+"/"+this.nFiles);
                    IJ.showProgress(count, this.nFiles);
                    if (count>=this.nFiles)
                        break;
                    if (IJ.escapePressed())
                    { IJ.beep(); break; }
                }  // open images as stack

            }
            catch (OutOfMemoryException e)
            {
                IJ.outOfMemory("FolderOpener");
                if (stack!=null) stack.trim();
            }
            if (stack!=null && stack.size()>0)
            {
                ImagePlus imp2 = new ImagePlus(title, stack);
                if (imp2.getType()==ImagePlus.GRAY16 || imp2.getType()==ImagePlus.GRAY32)
                    imp2.getProcessor().setMinAndMax(min, max);
                if (fi==null)
                    fi = new FileInfo();
                fi.fileFormat = FileInfo.UNKNOWN;
                fi.fileName = "";
                fi.directory = directory;
                imp2.setFileInfo(fi); // saves FileInfo of the first image
                imp2.setOverlay(overlay);
                if (stack is VirtualStack) {
                    var props = ((VirtualStack)stack).getProperties();
                    if (props!=null)
                        imp2.setProperty("FHT", props["FHT"]);
                }
                if (allSameCalibration)
                {
                    // use calibration from first image
                    if (this.scale!=100.0 && cal.scaled())
                    {
                        cal.pixelWidth /= this.scale/100.0;
                        cal.pixelHeight /= this.scale/100.0;
                    }
                    if (cal.pixelWidth!=1.0 && cal.pixelDepth==1.0)
                        cal.pixelDepth = cal.pixelWidth;
                    imp2.setCalibration(cal);
                }
                if (!string.IsNullOrEmpty(info1) && info1.LastIndexOf("7FE0,0010", StringComparison.Ordinal)>0)
                { //DICOM
                    if (_sortByMetaData)
                        stack = DicomTools.sort(stack);
                    imp2.setStack(stack);
                    double voxelDepth = DicomTools.getVoxelDepth(stack);
                    if (voxelDepth>0.0)
                    {
                        if (IJ.debugMode) IJ.log("DICOM voxel depth set to "+voxelDepth+" ("+cal.pixelDepth+")");
                        cal.pixelDepth = voxelDepth;
                        imp2.setCalibration(cal);
                    }
                    if (imp2.getType()==ImagePlus.GRAY16 || imp2.getType()==ImagePlus.GRAY32)
                    {
                        imp2.getProcessor().setMinAndMax(min, max);
                        imp2.updateAndDraw();
                    }
                }
                if (imp2.getStackSize()==1)
                {
                    int idx = this.start-1;
                    if (idx<0 || idx>=list.Length)
                        idx = 0;
                    imp2.setProp("Slice_Label", list[idx]);
                    if (info1!=null)
                        imp2.setProperty("Info", info1);
                }
                if ((arg==null||runningOpen) && !saveImage)
                {
                    String time = (DateTime.UtcNow.currentTimeMillis()-t0)/1000.0 + " seconds";
                    if (_openAsSeparateImages)
                    {
                        if (imp2.getStackSize()>MAX_SEPARATE && !IJ.isMacro())
                        {
                            bool ok = IJ.showMessageWithCancel("Import>Image Sequence",
                            "Are you sure you want to open "+imp2.getStackSize()
                            +" separate windows?\nThis may cause the system to become very slow or stall.");
                            if (!ok) return;
                        }
                        openAsSeparateImages(imp2);
                    }
                    else
                        imp2.Show(time);
                    if (stack.isVirtual())
                    {
                        overlay = stack.getProcessor(1).getOverlay();
                        if (overlay!=null)
                            imp2.setOverlay(overlay);
                    }
                }
                if (saveImage)
                    image = imp2;
		    }
		    IJ.showProgress(1.0);
            if (Recorder.OnRecord)
            {
                String options = _openAsVirtualStack&&!_openAsSeparateImages ? "virtual" : "";
                if (bitDepth!=defaultBitDepth)
                    options = options + " bitdepth=" + bitDepth;
                if (filter!=null && filter.Length>0)
                {
                    if (filter.Contains(" "))
                        filter = "["+filter+"]";
                    options = options + " filter=" + filter;
                }
                if (start!=1)
                    options = options + " start=" + start;
                if (step!=1)
                    options = options + " step=" + step;
                if (scale!=100)
                    options = options + " scale=" + scale;
                if (!_sortByMetaData)
                    options = options + " noMetaSort";
                if (!Recorder.scriptMode() && _openAsSeparateImages)
                    options = options + " open";
                //todo:
                // String dir = Recorder.fixPath(directory);
                // if (Recorder.scriptMode())
                //     Recorder.recordCall("imp = FolderOpener.open(\""+dir+"\", \""+options+"\");");
                // else
                // {
                //     if (options.length()==0)
                //         Recorder.recordString("File.openSequence(\""+dir+"\");\n");
                //     else
                //         Recorder.recordString("File.openSequence(\""+dir+"\", \""+options+"\");\n");
                //     Recorder.disableCommandRecording();
                // }
            }
	    }
	
        private void error(String msg)
        {
            IJ.error("Import>Image Sequence", msg);
        }

        private void openAsSeparateImages(ImagePlus imp)
        {
            VirtualStack stack = (VirtualStack)imp.getStack();
            String dir = stack.getDirectory();
            int skip = 0;
            for (int n = 1; n<=stack.size(); n++)
            {
                ImagePlus imp2 = IJ.openImage(dir+stack.getFileName(n));
                if (skip<=0)
                {
                    imp2.Show();
                    skip = imp2.getStackSize()-1;
                }
                else
                    skip--;
            }
        }

        public static bool useInfo(String info)
        {
            return info!=null && !(info.StartsWith("Software")||info.StartsWith("ImageDescription"));
        }

        private void openAsFileInfoStack(FileInfoVirtualStack stack, String path)
        {
            FileInfo[] info = Opener.getTiffFileInfo(path);
            if (info==null || info.Length==0)
                return;
            int n = info[0].nImages;
            if (info.Length==1 && n>1)
            {
                long size = fi.width*fi.height*fi.getBytesPerPixel();
                for (int i = 0; i<n; i++)
                {
                    FileInfo fi = (FileInfo)info[0].clone();
                    fi.nImages = 1;
                    fi.longOffset = fi.getOffset() + i*(size + fi.getGap());
                    stack.addImage(fi);
                }
            }
            else
            {
                FileInfo fi = info[0];
                if (fi.fileType==FileInfo.RGB48)
                {
                    for (int slice = 1; slice<=3; slice++)
                    {
                        FileInfo fi2 = (FileInfo)fi.clone();
                        fi2.sliceNumber = slice;
                        stack.addImage(fi2);
                    }
                }
                else
                    stack.addImage(fi);
            }
        }

        bool showDialog()
        {
            string options = Macro.GetOptions();
            if (options!=null)
            {  //macro
                string optionsOrig = options;
                options = options.Replace("open=", "dir=");
                options = options.Replace("file=", "filter=");
                options =  options.Replace("starting=", "start=");
                options =  options.Replace("number=", "count=");
                options =  options.Replace("increment=", "step=");
                options =  options.Replace("inc=", "step=");
                if (!options.Equals(optionsOrig))
                    Macro.setOptions(options);
                if (options.Contains("convert_to_rgb"))
                    this.bitDepth = 24;
            }
            String countStr = "---";
            if (!directorySet && options==null)
                directory = Prefs.get(DIR_KEY, IJ.getDir("downloads")+"stack/");
            if (directory!=null && !IJ.isMacro())
            {
                DirectoryInfo f = new DirectoryInfo(directory);
                string[] names = f.GetDirectories().Select(d=>d.FullName).ToArray();
                names = trimFileList(names);
                countStr = names!=null ? ""+names.Length : "---";
            }
            GenericDialog gd = new GenericDialog("Import Image Sequence");
            gd.setInsets(5, 0, 0);
            gd.addDirectoryField("Dir:", directory);
            gd.setInsets(2, 55, 10);
            gd.addMessage("drag and drop target", IJ.font10, Color.DarkGray);
            gd.addChoice("Type:", types, bitDepthToType(bitDepth));
            gd.addStringField("Filter:", "", 10);
            gd.setInsets(0, 55, 0);
            gd.addMessage("enclose regex in parens", IJ.font10, Color.DarkGray);
            gd.addNumericField("Start:", this.start, 0, 6, "");
            gd.addStringField("Count:", countStr, 6);
            gd.addNumericField("Step:", this.step, 0, 6, "");
            if (!IJ.isMacro() && !GraphicsEnvironment.isHeadless())
            {
                ArrayList v = gd.getStringFields();
                dirField = (TextBox)v[0];
                dirField.TextChanged += textValueChanged;
                filterField = (TextBox)v[1];
                filterField.TextChanged += textValueChanged;
                countField = (TextBox)v[2];
                v = gd.getNumericFields();
                startField = (TextBox)v[0];
                startField.TextChanged += textValueChanged;
                stepField = (TextBox)v[1];
                stepField.TextChanged += textValueChanged;
            }
            gd.addNumericField("Scale:", this.scale, 0, 6, "%");
            gd.addCheckbox("Sort names numerically", _sortFileNames);
            gd.addCheckbox("Use virtual stack", _openAsVirtualStack);
            gd.addCheckbox("Open as separate images", false);
            gd.addHelp(IJ.URL+"/docs/menus/file.html#seq1");
            gd.showDialog();
            if (gd.wasCanceled())
                return false;
            directory = gd.getNextString();
            gd.setSmartRecording(true);
            int index = gd.getNextChoiceIndex();
            bitDepth = typeToBitDepth(types[index]);
            filter = gd.getNextString();
            if (legacyRegex!=null)
                filter = "("+legacyRegex+")";
            gd.setSmartRecording(true);
            this.start = (int)gd.getNextNumber();
            countStr = gd.getNextString();
            double count = Tools.parseDouble(countStr);
            if (!Double.IsNaN(count))
                nFiles = (int)count;
            this.step = (int)gd.getNextNumber();
            if (this.step<1)
                this.step = 1;
            this.scale = gd.getNextNumber();
            if (this.scale<5.0) this.scale = 5.0;
            if (this.scale>100.0) this.scale = 100.0;
            _sortFileNames = gd.getNextBoolean();
            if (!_sortFileNames)
                _sortByMetaData = false;
            _openAsVirtualStack = gd.getNextBoolean();
            if (_openAsVirtualStack)
                scale = 100.0;
            _openAsSeparateImages = gd.getNextBoolean();
            if (_openAsSeparateImages)
                _openAsVirtualStack = true;
            if (!IJ.MacroRunning())
            {
                staticSortFileNames = _sortFileNames;
                if (!_openAsSeparateImages)
                    staticOpenAsVirtualStack = _openAsVirtualStack;
            }
            return true;
        }

        public static String[] getFilteredList(String[] list, String filter, String title)
        {
            bool isRegex = false;
            if (filter!=null && (filter.Equals("") || filter.Equals("*")))
                filter = null;
            if (list==null || filter==null)
                return list;
            if (title==null)
            {
                String[] list2 = new String[list.Length];
                for (int i = 0; i<list.Length; i++)
                    list2[i] = list[i];
                list = list2;
            }
            if (filter.Length>=2 && filter.StartsWith("(")&&filter.EndsWith(")"))
            {
                filter = filter.Substring(1, filter.Length-1);
                isRegex = true;
            }
            int filteredImages = 0;
            for (int i = 0; i<list.Length; i++)
            {
                if (isRegex && containsRegex(list[i], filter, title!=null&&title.Contains("(legacy)")))
                    filteredImages++;
                else if (list[i].Contains(filter))
                    filteredImages++;
                else
                    list[i] = null;
            }
            if (filteredImages==0)
            {
                if (title!=null)
                {
                    if (isRegex)
                        IJ.error(title, "None of the file names contain the regular expression '"+filter+"'.");
                    else
                        IJ.error(title, "None of the "+list.Length+" files contain '"+filter+"' in the name.");
                }
                return null;
            }
            String[] list3 = new String[filteredImages];
            int j = 0;
            for (int i = 0; i<list.Length; i++)
            {
                if (list[i]!=null)
                    list3[j++] = list[i];
            }
            list = list3;
            return list;
        }

        public static bool containsRegex(String name, String regex, bool legacy)
        {
            bool contains = false;
            try
            {
                if (legacy)
                    contains = new Regex(regex).IsMatch(name);
                else
                    contains = name.Replace(regex, "").Length!=name.Length;
                IJ.ShowStatus("");
            }
            catch (Exception e)
            {
                String msg = e.Message;
                int index = msg.IndexOf("\n");
                if (index>0)
                    msg = msg.Substring(0, index);
                IJ.ShowStatus("Regex error: "+msg);
                contains = true;
            }
            return contains;
        }

        private int typeToBitDepth(String type)
        {
            int depth = 0;
            if (type.Equals("16-bit")) depth=16;
            else if (type.Equals("32-bit")) depth=32;
            else if (type.Equals("RGB")) depth=24;
            return depth;
        }

        private String bitDepthToType(int bitDepth)
        {
            switch (bitDepth)
            {
                case 0: return types[0];
                case 16: return types[1];
                case 32: return types[2];
                case 24: return types[3];
            }
            return types[0];
        }

        /** Removes names that start with "." or end with ".db", ".txt", ".lut", "roi", ".pty", ".hdr", ".py", etc. */
        public String[] trimFileList(String[] rawlist)
        {
            if (rawlist==null)
                return null;
            int count = 0;
            for (int i = 0; i< rawlist.Length; i++)
            {
                String name = rawlist[i];
                if (name.StartsWith(".")||name.Equals("Thumbs.db")||excludedFileType(name))
                    rawlist[i] = null;
                else
                    count++;
            }
            if (count==0) return null;
            String[] list = rawlist;
            if (count<rawlist.Length)
            {
                list = new String[count];
                int index = 0;
                for (int i = 0; i< rawlist.Length; i++)
                {
                    if (rawlist[i]!=null)
                        list[index++] = rawlist[i];
                }
            }
            return list;
        }

        /* Returns true if 'name' ends with ".txt", ".lut", ".roi", ".pty", ".hdr", ".java", ".ijm", ".py", ".js" or ".bsh. */
        public static bool excludedFileType(String name)
        {
            if (name==null) return true;
            for (int i = 0; i<excludedTypes.Length; i++)
            {
                if (name.EndsWith(excludedTypes[i]))
                    return true;
            }
            return false;
        }

        public void openAsVirtualStack(bool b)
        {
            _openAsVirtualStack = b;
        }

        public void sortFileNames(bool b)
        {
            _sortFileNames = b;
        }

        public void sortByMetaData(bool b)
        {
            _sortByMetaData = b;
        }

        /** Used by DragAndDrop when a directory is dragged onto the ImageJ window. */
        public void setDirectory(String path)
        {
            directory = path;
            directorySet = true;
        }

        /** Sorts file names containing numerical components.
        * @see ij.util.StringSorter#sortNumerically
        * Author: Norbert Vischer
        */
        public String[] sortFileList(String[] list)
        {
            return StringSorter.sortNumerically(list);
        }

        private void DirFieldOnTextChanged(object? sender, EventArgs e)
        {
            throw new NotImplementedException();
        }

        public void textValueChanged(object? sender, EventArgs e)
        {
            if (dirField==null)
                return;
            String dir = dirField.Text;
            DirectoryInfo f = new DirectoryInfo(dir);
            String[] names = f.GetDirectories().Select(d=>d.FullName).ToArray();
            names = trimFileList(names);
            names = getFilteredList(names, filterField.Text, null);
            int count = names!=null ? names.Length : 0;
            double start = Tools.parseDouble(startField.Text, Double.NaN);
            if (!double.IsNaN(start) && start>1)
                count = count - ((int)start-1);
            double step = Tools.parseDouble(stepField.Text, Double.NaN);
            if (!Double.IsNaN(step) && step>1)
                count = count/(int)step;
            String countStr = count>0 ? ""+count : "---";
            countField.Text = countStr;
        }

} // FolderOpener



}
